//
//  BZAudioRecorder.m
//  BZFoundation
//
//  Created by 郭艺伟 on 2017/9/28.
//  Copyright © 2017年 郭艺伟. All rights reserved.
//

#import "BZAudioRecorder.h"
#import "BZNotificationController.h"
#import <AVFoundation/AVFoundation.h>
#import "UIDevice+BZHelper.h"

#import "BZAudioUtility.h"

#import "amrFileCodec.h"

NSString * const BZAudioRecordErrorDomain = @"com.BZ.BZAudioRecorderErrorDomain";

@interface BZAudioRecorder () <AVAudioRecorderDelegate>

@property (nonatomic, strong) AVAudioRecorder *recorder;
@property (nonatomic, strong) NSDictionary *recordSetting;

@property (nonatomic, copy, readwrite) NSURL *latestFilePath;

@property (nonatomic, assign) BOOL  isConverting;

@property (nonatomic, assign) BOOL  isCanceled;

@end
@implementation BZAudioRecorder

static dispatch_once_t onceToken;
static BZAudioRecorder *instance = nil;

#pragma mark - Singleton

+ (instancetype)sharedInstance {
    dispatch_once(&onceToken, ^ {
        instance = [[self alloc] initSingleton];
    });
    return instance;
}
- (id)init {
    NSAssert(NO, @"Cannot create instance of Singleton");
    return nil;
}

- (id)initSingleton {
    if (self = [super init]) {
        _minimumDuration = 1.f;
        _maximumDuration = 120.f;
        [self.notificationController observeNotification:AVAudioSessionInterruptionNotification
                                                  object:nil
                                                selector:@selector(handleInterruptionNotification:)];
    }
    return self;
}

#pragma mark - Interruption Notification

- (void)handleInterruptionNotification:(NSNotification *)notification {
    if (!self.recorder) {
        return;
    }
    
    NSInteger interruptionType = [notification.userInfo[@"AVAudioSessionInterruptionTypeKey"] integerValue];
    if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
        if ([self.delegate respondsToSelector:@selector(audioRecorderDidBeginInterruption:)]) {
            [self.delegate audioRecorderDidBeginInterruption:self];
        }
    }
    else if(interruptionType == AVAudioSessionInterruptionTypeEnded) {
        if ([self.delegate respondsToSelector:@selector(audioRecorderDidEndInterruption:)]) {
            [self.delegate audioRecorderDidEndInterruption:self];
        }
    }
}

#pragma mark - Record

- (BOOL)recordWithError:(NSError **)error {
    self.isCanceled = NO;
    return [self recordWithError:error conversionCompleteBlock:nil];
}

- (BOOL)recordWithError:(NSError **)error conversionCompleteBlock:(BZAudioRecordConversionBlock)callback {
    self.isCanceled = NO;
    NSError *theError;
    
    if (self.recorder.isRecording) {
        // 正在录音
        if (error) {
            theError = [NSError errorWithDomain:BZAudioRecordErrorDomain
                                           code:BZAudioRecordErrorTypeIsRecording
                                       userInfo:@{NSLocalizedDescriptionKey: @"Microphone is already working."}];
            *error = theError;
        }
        return NO;
    }
    
    if (![AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]) {
        // 找不到麦克风
        if (error) {
            theError = [NSError errorWithDomain:BZAudioRecordErrorDomain
                                           code:BZAudioRecordErrorTypeFoundMic
                                       userInfo:@{NSLocalizedDescriptionKey: @"Microphone not found."} ];
            *error = theError;
        }
        return NO;
    }
    
    if (![UIDevice bz_canRecord]) {
        // 无法使用使用麦克风
        if (error) {
            theError = [NSError errorWithDomain:BZAudioRecordErrorDomain
                                           code:BZAudioRecordErrorTypeAccessMic
                                       userInfo:@{NSLocalizedDescriptionKey: @"Can't access microphone."} ];
            *error = theError;
        }
        return NO;
    }
    
    if (![[AVAudioSession sharedInstance] setActive:YES error:error]) {
        return NO;
    }
    
    if (![[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
                                                error:error]) {
        return NO;
    }
    
    NSURL *currentFilePath = [self uniqueFilePathWithExtension:@"wav"];
    self.recorder = [[AVAudioRecorder alloc] initWithURL:currentFilePath
                                                settings:self.recordSetting
                                                   error:&theError];
    if (!self.recorder) {
        if (error) {
            *error = theError;
        }
        return NO;
    }
    
    if(![self.recorder prepareToRecord]) {
        if (error) {
            theError = [NSError errorWithDomain:BZAudioRecordErrorDomain
                                           code:BZAudioRecordErrorTypeCreateFile
                                       userInfo:@{NSLocalizedDescriptionKey: @"Creates an audio file at the location specified by the url fail."} ];
            *error = theError;
        }
        return NO;
    }
    
    if (self.isConverting) {
        if (error) {
            theError = [NSError errorWithDomain:BZAudioRecordErrorDomain
                                           code:BZAudioRecordErrorTypeConvertion
                                       userInfo:@{NSLocalizedDescriptionKey: @"Audio recorder is converting file and fail to record"} ];
            *error = theError;
        }
        return NO;
    }
    
    self.recorder.delegate = self;
    self.recorder.meteringEnabled = YES;
    self.latestFilePath = currentFilePath;
    [self.recorder recordForDuration:self.maximumDuration];
    
    if ([self.delegate respondsToSelector:@selector(audioRecorderDidBeginRecord:)]) {
        [self.delegate audioRecorderDidBeginRecord:self];
    }
    
    
    
    if (callback) {
        self.isConverting = YES;
        changeStateTo(1);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSURL *amrURL = [self uniqueFilePathWithExtension:@"amr"];
            NSError *error;
            bool conversionResult = [BZAudioUtility convertWavFile:self.latestFilePath
                                                          toAmrFile:amrURL
                                                              error:&error];
            dispatch_async(dispatch_get_main_queue(), ^{
                callback(conversionResult ? amrURL : nil, error);
                self.isConverting = NO;
            });
        });
    }
    return YES;
}

- (BOOL)isRecording {
    return self.recorder.isRecording;
}

- (void)pause {
    [self.recorder pause];
}

- (void)resume {
    [self.recorder record];
}

- (void)stop {
    [self.recorder stop];
    changeStateTo(0);
}

- (void)cancel {
    [self.recorder stop];
    self.isCanceled = YES;
    [[NSFileManager defaultManager] removeItemAtPath:self.recorder.url.path error:nil];
}

#pragma mark - Power

- (CGFloat)averagePower {
    return [self averagePowerWithChannel:0];
}

- (CGFloat)averagePowerWithChannel:(NSUInteger)channelNumber {
    [self.recorder updateMeters];
    return [self.recorder averagePowerForChannel:channelNumber];
}

#pragma mark - Setter & Getter

- (void)setMinimumDuration:(NSTimeInterval)minimumDuration {
    if (minimumDuration < 1.f) {
        return;
    }
    _minimumDuration = minimumDuration;
}

- (void)setMaximumDuration:(NSTimeInterval)maximumDuration {
    if (maximumDuration < _minimumDuration) {
        return;
    }
    _maximumDuration = maximumDuration;
}

- (NSTimeInterval)currentTime {
    return self.recorder.currentTime;
}

- (NSURL *)audioDirectory {
    if (!_audioDirectory) {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        NSString *audioPath = [documentsPath stringByAppendingPathComponent:@"AudioTmp"];
        if (![fileManager fileExistsAtPath:audioPath]) {
            [fileManager createDirectoryAtPath:audioPath withIntermediateDirectories:NO attributes:nil error:nil];
        }
        
        return [NSURL fileURLWithPath:audioPath];
    }
    return _audioDirectory;
}

- (NSURL *)uniqueFilePathWithExtension:(NSString *)extension {
    NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
    NSString *fileName = [NSString stringWithFormat:@"%f", timeInterval];
    fileName = [fileName stringByReplacingOccurrencesOfString:@"." withString:@"_"];
    fileName = [fileName stringByAppendingString:@"."];
    fileName = [fileName stringByAppendingString:extension];
    NSURL *filePath = [self.audioDirectory URLByAppendingPathComponent:fileName];
    return filePath;
}

- (NSDictionary *)recordSetting {
    if (!_recordSetting) {
        NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] initWithCapacity:5];
        recordSetting[AVFormatIDKey] = @(kAudioFormatLinearPCM);
        recordSetting[AVSampleRateKey] = @8000;
        recordSetting[AVNumberOfChannelsKey] = @1;
        recordSetting[AVEncoderAudioQualityKey] = @(AVAudioQualityMedium);
        recordSetting[AVLinearPCMBitDepthKey] = @16;
        _recordSetting = [recordSetting copy];
    }
    return _recordSetting;
}

#pragma mark - AVAudioRecorderDelegate

- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder
                           successfully:(BOOL)flag {
    changeStateTo(0);
    if (self.isCanceled) {
        return ;
    }
    NSError *error = nil;
    NSTimeInterval duration = [BZAudioUtility wavDurationWithFilePath:recorder.url];
    if (duration <= self.minimumDuration/* todo hex:判断的意义是？ && duration != 0.f*/) {
        error = [NSError errorWithDomain:BZAudioRecordErrorDomain
                                    code:BZAudioRecordErrorTypeAudioDuringShort
                                userInfo:@{NSLocalizedDescriptionKey: @"Audio duration is too short."} ];
    }
    
    if ([self.delegate respondsToSelector:@selector(audioRecorderDidFinishRecording:filePath:error:)]) {
        [self.delegate audioRecorderDidFinishRecording:self
                                              filePath:flag ? self.recorder.url : nil
                                                 error:error];
    }
}

#pragma mark - Test

+ (void)resetSharedInstance {
    onceToken = 0; // resets the once_token so dispatch_once will run again
    instance = nil;
}


@end
